The Audit Library allows you to add event tracing to all types of Macintosh code segments, including device drivers, callback routines, dynamically-loaded code segments, and applications. Except for the initialization routine, all functions may be called at any time, even within interrupt or I/O completion routines. The library is designed to be “fail-safe:” it has no error conditions and, barring a malicious attack, will either do what is requested or do nothing, but it should never crash or stall your program. In particular, if your application tries to log data when no audit record has been established, or there is no room in the audit record, your program will not stall, crash or damage the system.
USAGE
To use the Audit library, you must include its definition file, create an Audit record, store data, and read data. The following examples show this in the context of a single application; however real-world users would probably split the functions among separate applications; perhaps by creating the Audit record in an init or driver open routine, storing data in a driver or callback code-segment, and displaying data in a stand-alone application or MacsBug dcmd.
Global Variable Definitions
A very simple Audit display application might define the parameters it needs as global values:
#include "Audit.h"
AuditPtr gAuditPtr; /* Returned by InitAudit and ReadAudit */
AuditEntry gCurrentEntry; /* Returned by ReadAudit */
ProcessSerialNumber gProcessSerialNumber; /* Used by WakeUpAudit */
Boolean gHasPSN; /* See WakeUpAudit example */
Creating the Audit Record
Using the above definitions, the following statements create and initialize an audit record:
The above call creates an audit record “named” Moof that can store 64 audit record entries. Auditing is initially enabled. If there is no room in the audit record, the first entry waiting for the display routine will be used for this entry (i.e., the last 64 entries will be retained). Note that InitAudit may only be called from applications or code-segments that can allocate memory. In the following example, it is called from a display application that also initializes the time format parameter handle and records the start times.
void
InitializeAudit(
OSType auditSelector
)
{
/*
* Create an audit record that can hold 64 entries. Logging is
Under System 7, the display application should inform the Audit library that it is to be awakened from its event loop whenever data is stored in the audit record. This allows the display application to set a long sleep time when it calls WaitNextEvent. In the following example, StartAuditing should be called immediately after your display application creates an audit record (or calls GetAuditPtr to determine that an audit record already exists). StopAuditing should be called before your application exits.
Once your application calls StartAuditing, it will receive an event whenever anything calls Audit for this audit record.
Writing an Audit Record Entry
The following code sequence shows how the library might be used to log status errors. It would be called immediately after calling a library function. In the example, the application tries to read data from a file and logs any errors that are returned:
status = FSRead(refNum, &bytesToRead, buffer);
if (status != noErr) {
if (status != eofErr) {
/*
* Unexpected error
*/
AuditStatusLocation(gAuditPtr, 'Read', status);
Audit(
gAuditPtr,
'Read',
AuditFormat5(
kAuditFormatSigned,
kAuditFormatSigned,
kAuditFormatUnsigned,
kAuditFormatAddress
kAuditFormatString
),
(signed long) status,
(signed long) refNum,
bytesToRead,
buffer,
"\pFSRead"
);
}
}
This example shows two Audit calls: the AuditStatus macro stores the status code and a string that identifies the function that called it, while the general Audit call stores five values: the status code, device reference number, number of bytes, buffer address, and a labelling string.
Reading an Audit Record Entry
A typical display application calls ReadAudit each time within its event loop, even if no event was returned:
for (i = 0; i < 10; i++) {
if (ReadAudit(gAuditPtr, &gMissedDataCount, &gCurrentEntry) == FALSE)
break; /* Nothing more to display */
ProcessAuditEntry();
}
Here, note that each pass through the event loop tries to process several audit records, exiting when the “to be done” queue is empty, or a reasonable number have been processed. Processing entries in a loop such as the one shown above may prevent a run-away asychronous process from absorbing the entire machine by saturating the audit record.
Your display application would process an audit entry by formatting the entry record, including the timestamp, id code, and data.
Displaying the Audit Entry Data
Two functions are provided (in file AuditEntryFormat.c) that convert the audit entry record into readable Pascal strings.
• The FormatAuditEntryTimestamp function determines the time that an audit entry was created, storing it in a Pascal string buffer. The time is given in ISO-standard date format as “yyyy.mm.dd hh.mm.ss.msec.” This is a fixed-length string whose format is independent of the user’s time and date formatting choice.
• The FormatAuditData function converts the data portion of the audit entry record, storing it as a Pascal string.
For example, the following C sequence may be used to print an audit entry record:
void
FormatEntryData(void)
{
Str255 timestamp;
Str255 content;
#define ENTRY (gCurrentEntry)
/*
* gLogIndex is the sequence number of this entry. Note that it tracks
* missing entries: i.e. it records the sequence of Audit calls.
The source of FormatAuditEntryTimestamp and FormatAuditEntryData should be consulted to see how to process the entry data, including converting the timestamp and “decompiling” the formatted data. The printf format writes the pascal string in a format compatible with the ANSI standard. Think C users can also use an implementation-specific format designator for Pascal strings: "%#s".
AUDIT RECORD DATA
The audit library uses a private data area in the System Heap to store its information. InitAudit and GetAuditPtr return a pointer to that area, and all other functions use that pointer as a parameter. The contents of the area are private: your application does not access this record directly, but calls library routines. This is important to prevent asychronous calls (from interrupt routines, for example) from accessing the data simultaneously.
Your display application does need to understand the format of the audit entry record that is returned by ReadAudit. This contains the information that was stored as a result of an Audit call.
Audit Entry Contents
ReadAudit. if successful, returns an audit record entry. This is a C structure with the following format:
typedef struct AuditEntry {
unsigned long tickCount; /* TickCount() at call */
unsigned long lostData; /* Lost record count */
OSType idCode; /* Why are we logging */
unsigned long format; /* Format of the data */
unsigned long data[8]; /* 32-bytes of data */
} AuditEntry, *AuditEntryPtr;
The structure elements are used as follows:
tickCount This value timestamps each entry: it is the value of Ticks when the element was written into the log.
lostData This is set to the number of entries that were not logged because the log area was full. Each time Audit stores a log entry, it copies its internal lostData counter to the new audit entry, then sets its internal counter to zero. This means that the lostData counter is synchronized with data logging: if it is non-zero, that number of Audit calls were unsuccessful immediately before this record.
idCode This value is the idCode parameter when Audit was called to store this record. Your application may use it for any purpose however, by convention, it is an OSType (four character string) that further identifies the Audit call.
format This is the format parameter when Audit was called to store this record. It defines the format of the rest of the record.
data This contains the actual data stored by the Audit call. There is enough space for up to eight longwords. Some format parameters store a string which can take up the remaining space. For example, if the only parameter is kAuditFormatString, up to 31 bytes (plus a one-byte length code) may be stored.
While processing this data is not especially tricky, you may wish to re-read the sample code to unserstand how to format the data values.
Audit RECORD Contents
The Audit library keeps all information it needs in private structures. It is essential to understand that user applications must not modify these structure directly: they are described here for developers who need to modify the library for their own specific purposes. Note also that the following has been simplified slightly: the actual AuditRecord has some additional structures to preserve compatibility between Think C and MPW compilation conventions.
typedef struct AuditQueueEntry {
QElemPtr qLink; /* Queue linkage */
AuditEntry theEntry; /* User’s data area */
} AuditQueueEntry, *AuditQueueEntryPtr;
typedef struct AuditRecord {
union {
void *unusedLongword;
struct {
unsigned short low; /* Earliest lib version */
unsigned short high; /* Latest lib version */
} u;
} version;
unsigned long recordSize; /* Compiler check */
unsigned long lostData; /* Missed log counter */
void *refNum; /* User-controlled long */
unsigned long flags; /* Logging & preference */
unsigned long timeAtStart; /* GetDateTime() */
unsigned long ticksAtStart; /* TickCount() */
ProcessSerialNumber PSN; /* Wakeup this process */
unsigned long logicalRAMSize; /* From Gestalt */
QHdr freeQueue; /* Free queue header */
QHdr dataQueue; /* Busy queue header */
AuditQueueEntry entries[1]; /* Entries stored here */
} AuditRecord, *AuditPtr;
The structure elements are used as follows:
version This 32-bit value permits different versions of the Audit library to co-exist. InitAudit sets these values. Then, all calls to GetAuditPtr will verify that this instance of the library supports this AuditRecord format (I.e., is backwards compatible with earlier versions). version.u.low is the earliest library version supported; version.u.high is the latest. Each short is divided, by convention, into “major” and “minor” release identification values.
If you modify the AuditRecord or AuditEntry definitions, your version of InitAudit should change its version identifier so that your AuditRecord will co-exist peacefully with other records that may be on your system.
recordSize This 32-bit value contains the sizes of the AuditRecord and AuditEntry. It prevents the program from crashing if a compiler changes the way it organizes data in memory.
If you modify the AuditRecord or AuditEntry defintions, this value will be recomputed.
lostData This counter maintains the internal “lost data” counter. It is incremented whenever Audit cannot store an entry because there are no entries in the free queue, and is cleared to zero whenever Audit successfully stores an audit entry.
flags This word contains two flag bits: kAuditEnableMask is non-zero if logging is enabled. kAuditPreserveFirstMask is non-zero if data overflow should retain the first elements in the log. The flags variable is stored as a longword so that all record elements are aligned to 32-bit boundaries: this improves performance on modern hardware.
timeAtStart When the AuditRecord is created, this element receives the system time (seconds since the epoch).
ticksAtStart When the AuditRecord is created, this element receives the system ticks value.
PSN This identifies the process to awaken when data is stored in the Audit log. If the high longword is zero and the low longword is kNoProcess, no process will be awakened.
logicalRAMSize This is set to the end of logical RAM when the AuditRecord is created. Audit uses it to prevent a garbage address passed to the Audit string formatter from causing an address exception.
freeQueue This operating system queue stores the audit entries that are waiting to be filled by calls to Audit.
dataQueue This operating system queue stores the audit entries that have been filled and are waiting to be displayed by an application that calls ReadAudit.
entries[] This is a vector of AuditQueueEntry records that the Audit function uses to store data. An AuditQueueEntry is an AuditEntry prefaced by the Operating System Queue linkage pointer.
InitAudit and GetAuditPtr return a pointer to the start of the AuditRecord. For reference, this record is preceded by a very small code segment that the system Gestalt manager calls when an application calls Gestalt. That code segment returns a pointer to the audit record. See the comments in Audit.c for details.
FUNCTION USAGE
The Audit library contains functions to create audit records, store data in an audit record, and extract data from an audit record. In addition, a number of functions that access records within an audit record are provided so the library can evolve without requiring application program modification and, especially, so that the library modifies its internal structures in a way that prevents two asychronous requests from changing a structure element at the same time.
Creating an Audit Record
AuditPtr
InitAudit(
OSType gestaltSelector,
unsigned short nEntries,
Boolean initiallyEnabled,
Boolean preserveFirstEntries
);
InitAudit creates a new audit record as a non-relocatable object in the System Heap that is “named” by the gestaltSelector. The record will contain the indicated number of audit record entries. The initiallyEnabled parameter allows the caller choose whether to start logging when the record is created. If preserveFirstEntries is TRUE, Audit will not store data if there are no entries in the free queue. If preserveFirstEntries is FALSE, Audit will attempt to store data, even if it has to remove an entry from the "waiting for display" queue. InitAudit returns a pointer to the audit record it created or found, or NULL if it cannot create a record.
As far as the Macintosh operating system is concerned, an audit record is a Gestalt selector function. This means that, once created, it cannot be deleted and its size cannot be changed. When creating a record, InitAudit will check for a pre-existing record, returning a pointer to that record if it exists. Thus, your gestaltSelector must not conflict with any existing selectors. Using your application’s creator ID might be appropriate.
Several independent audit records can be active at any time, subject only to memory limitations and good taste.
Finding an Existing Audit Record
AuditPtr
GetAuditPtr
OSType gestaltSelector
);
GetAuditPtr returns a pointer to the audit record, or NULL if no record is defined for this selector. It also returns NULL if Gestalt returned a value that did not satisfy several internal consistency checks. This might happen if your program calls GetAuditPtr for a system Gestalt value, or if there was a severe version mismatch between the library that created the audit record and the library that is attempting to access it.
Your application would typically call this function when it starts since, if an audit record is present, it will remain until your system is shutdown or restarted. The value returned by GetAuditPtr is used as a parameter to all other functions. Although your application could call GetAuditPtr each time it tries to log some data, this requires an internal call to Gestalt which would be inefficient.
Writing an Audit Record Entry
void
Audit(
AuditPtr auditPtr,
OSType idCode,
unsigned long format,
... /* Additional parameters, if any */
);
The Audit function stores an entry in the audit record that contains the following data:
• Audit timestamps each entry with the system Ticks() value. As will be seen, your display procedure can convert this value into civil time (hours, minutes, seconds) when the entry is displayed.
• idCode is a value that you specify. By convention, it is assumed to be an OSType that the display application uses to identify the audit request. This may be a unique value for each request or a value that is common to a group of requests (such as a single function or function library). Audit does not interpret this value, however the MacsBug display routine (dcmd) interpretes it as an OSType. Note that, on the Macintosh, an OSType can be coerced to any longword scalar, such as a memory address. As you adapt the Audit library to your own use, you may wish to consider passing some state information in the idCode parameter (perhaps a pointer to a task-specific handle) rather than using it as a human-readable identifier.
• format is a 32-bit value that the Audit function interprets to understand how to process the additional parameters, if any. It should be set to the result of expanding the AuditFormat macro, or one of two special values. It will be described below.
• Your program up to eight longword arguments. The format parameter tells Audit and the display procedure how to interpret these arguments, if any.
Note: because of differences between various C compilers, it is essential that your program specify all data parameters as longwords. For example, if you wish to log a value that is normally stored as a short value, such as a system error code or Boolean, your program must explicitly cast it to long or unsigned long. If you don’t do this, you will display incorrect data. The Audit library operates compatibly under both MPW and Think C. An application written in one environment can access an Audit Record created by the other environment.
Of course, Audit will only store an entry if several conditions are met:
• AuditPtr must be non-NULL. It is the value returned by GetAuditPtr or InitAudit. If it is NULL, Audit silently returns.
• The version number and record size values must be acceptable.
• Auditing must have been enabled, either by InitAudit or EnableAudit.
• There must be room for the entry in the audit entry area. This test will fail if your display function does not read audit record entries quickly enough and will be discussed further.
Formatting Audit parameters
In order to simplify audit display applications, each audit record entry is self-formatting. The format parameter specifies the format of all additional parameters. The format parameter should be constructed by expanding the AuditFormat macro. For most uses, you would use AuditFormat1, AuditFormat2, etc. which call AuditFormat with the proper argument sequence.
The following, if present, must be the last — or only — AuditFormat argument specification:
kAuditFormatString This must be the last parameter in an AuditFormat specification. Audit requires one additional parameter, a Pascal string. If it is the only parameter, the first 31 bytes of the string will be stored. If other parameters preceed this, as many bytes as can fit will be stored (each argument requires four bytes). Your program does not need to concern itself with string length; the Audit function truncates the data as needed. Note: if the parameter is bad (NULL or greater than the logical RAM size, it will display “?NN” where NN is the value of the argument in Hex
kAuditFormatStatusLocation This must be the last parameter in an AuditFormat specification. There is no associated parameter. The Audit function stores the name and offset of the caller in the audit record, truncating the string as needed.
The following may appear anywhere in the AuditFormat operation. Each argument defines the format of an associated Audit parameter:
kAuditFormatSigned A signed integer longword value.
kAuditFormatUnsigned An unsigned integer longword value.
kAuditFormatHex An unsigned hexadecimal value that may be interpreted as a 4-byte character, such as an OSType or ResType value. This is displayed both in hexadecimal and as a character string (with ‘.’ replacing any non-printable bytes).
kAuditFormatAddress An unsigned hexadecimal value that is never interpreted as a character string.
kAuditFormatEnd This terminates the list of parameters. The AuditFormat1, etc. macros append this as needed: by using these macros, you do not need to be concerned with this value.
In order to simplify the life of the poor programmer, the header file provides a family of macros that allow you to specify one to eight arguments. Thus, instead of writing
Note that the function call explicitly casts the integer parameter to long.
The header file provides AuditFormat1, AuditFormat2, …, AuditFormat8 macros.
Writing a String to the Audit Record
void
AuditString(
AuditPtr auditPtr,
OSType idCode,
const StringPtr string
);
The AuditString function stores a string in a single Audit record entry. It is actually implemented as a macro as follows:
#define AuditString(auditPtr, idCode, string) ( \
Audit( \
(auditPtr), \
(idCode), \
AuditFormat1(kAuditFormatString), \
string \
) \
)
Writing a Status Error to the Audit Record
void
AuditStatusString(
AuditPtr auditPtr,
OSType idCode,
OSErr status,
const StringPtr string
);
The AuditStatusString function stores an operating-system status code and accompaning descriptive string in a single Audit record entry.
void
AuditStatusLocation(
AuditPtr auditPtr,
OSType idCode,
OSErr status
);
The AuditStatusLocation function stores an operating-system status code and the location of the call in a single Audit record entry. The location uses the MacsBug name and is similar to the string that would be displayed by the MacsBug ‘sc6’ command.
Both AuditStatusString and AuditStatusLocation are implemented as macros that call Audit with the proper format expression.
Reading an Audit Record Entry
Boolean
ReadAudit(
AuditPtr auditPtr,
AuditEntryPtr thisLogEntry
);
If there is an audit record entry waiting for this audit record, ReadAudit copies it to your entry buffer and returns TRUE. If nothing is waiting (or auditPtr is NULL), it returns FALSE. Note that ReadAudit does not care whether audit logging is currently enabled.
If ReadAudit returns FALSE, no additional data is returned.
ReadAudit copies its internal audit entry to the buffer that your program specifies. Then it immediately returns the buffer to the free buffer list . A subsequent section of this document describes the content of an AuditEntry record.
Formatting the Audit Record Entry Data
void
FormatAuditEntryData(
AuditEntryPtr entryPtr,
StringPtr result
);
FormatAuditEntryData converts the data in the entry to a readable string, storing the formatted output in the result string. It does not display the data, nor does it convert the timestamp. The FormatAuditEntryData function is provided in AuditEntryFormat.c. It may need to be mofified for non-English usage.
Formatting the Audit Record Entry Timestamp
void
FormatAuditEntryTimestamp(
AuditPtr auditPtr,
AuditEntryPtr entryPtr,
StringPtr result
);
FormatAuditEntryTimestamp converts the entry’s timestamp to a readable string, storing the formatted output in result. The output is a fixed-length string with the following format:
1992.12.25 22:10:44.123
The result contains the year, month, and date (in ISO-date format), followed by the time of day in 24-hour clock format with the residual clock ticks (in milliseconds). This format is independent of the time and date formatting selected by the computer user. The FormatAuditEntryTimestamp function is provided in AuditEntryFormat.c.
Support Functions
Your application should use the following functions to access an audit record’s internal structure. They also make it possible to extend the library without requiring changes in an application program. All audit record structure elements must be accessed through these functions as they prevent two interrupt-driven code sequences from accessing structure elements simultaneously.
Specifying the Display Application
void
WakeUpAudit(
AuditPtr auditPtr,
ProcessSerialNumber *oldPSN
);
If your application is running under a version of the operating system that supports the Process Manager, it can specify the application that should run whenever an entry is written into this audit record. Your application should set its process serial number in oldPSN before calling WakeUpAudit. On return, oldPSN is set to the previous display application.
Enabling and Disabling Audit Logging
Boolean
EnableAudit(
AuditPtr auditPtr,
Boolean enableLogging
);
If enableLogging is TRUE, calls to Audit will store data in the audit record (assuming, of course, that there is room to store data). If the parameter is FALSE, Audit will ignore calls for this audit record. EnableAudit returns the old value of the enabling flag; thus your application can restore the previous audit log state.
Testing Whether Audit Logging is Enabled
Boolean
IsAuditEnabled(
AuditPtr auditPtr
);
This function returns the current value of the enable flag. It returns FALSE if auditPtr is NULL or audit logging is disabled.
Controlling Data Overrun
Boolean
PreserveAudit(
AuditPtr auditPtr,
Boolean preserveFirst
);
If preserveFirst is TRUE and Audit is called when there is no room to store data, Audit will ignore the call request, preserving the entries waiting to be displayed. If preserveFirst is FALSE, Audit will throw away the first (earliest) entry in the “to be displayed” queue, thus retaining the latest nEntries worth of data. PreserveAudit returns the old value of the preserveFirst flag; thus your application can restore the previous audit state. Note that there is no “right” value for this flag: different applications require different preservation strategies.
Retrieving Audit Log Creation Times
void
GetAuditStartTimes(
AuditPtr auditPtr,
unsigned long *timeAtStart,
unsigned long *ticksAtStart
);
Your display application should call this function to get the actual time that the audit record was created. Using these values and the timestamp returned in each audit entry record, your display application can convert the audit entry timestamp to civil time (date and time).
Accessing the User-controlled Reference Value
void *
SetAuditRefNum(
AuditPtr auditPtr,
void *refNum
);
void *
GetAuditRefNum(
AuditPtr auditPtr
);
This pair of functions allows your application to set and retrieve a user-controlled value that may be coerced to any scalar value (such as a memory pointer). SetAuditRefNum returns the previous refNum value. Both functions return zero if auditPtr is NULL or no value had been stored. Your application may use this value for any purpose.